namespace SPDeploy.Library.Processing.Processors
{
    using System;
    using System.IO;
    using Data;
    using Exceptions;
    using Helpers;
    using Library;
    using Microsoft.SharePoint.Administration;

    public class SolutionProcessor : ProcessorBase<Solution, SPSolution>
    {
        #region Properties: public

        [ProcessorSetting(DefaultValue = IgnorableBehaviors.Ignore)]
        public IgnorableBehaviors AlreadyDeleted { get; set; }

        [ProcessorSetting(DefaultValue = DuplicateBehaviors.Error)]
        public DuplicateBehaviors Duplicate { get; set; }

        public override string Name
        {
            get { return "Solution"; }
        }

        #endregion

        #region Methods: public

        public override SPSolution Install(ProcessorContext ctx, Solution config)
        {
            Log(LogSeverity.Step, "Installing solution '{0}' to local farm", config.FileName);

            SPFarm farm = GetFarm();
            if (config.FileName.IsEmpty())
            {
                throw new DeploymentTargetConfigException("Filename cannot be empty for Solution config nodes");
            }
            if (!File.Exists(config.FileName))
            {
                throw new DeploymentTargetConfigException("Solution file '" + config.FileName + "' cannot be found on the specified path");
            }

            string solutionName = GetSolutionShortName(config);
            if (DoesFarmContainSolutionWithName(farm, solutionName))
            {
                switch (Duplicate)
                {
                    case DuplicateBehaviors.Error:
                        throw new DeploymentTargetConfigException("A solution with the same name (" + solutionName + ") already exists in the solution store.");

                    case DuplicateBehaviors.Overwrite:
                        Log(LogSeverity.SubStep, "Solution already exists - config instructed an overwrite");
                        RemoveSolution(farm, solutionName);
                        break;

                    case DuplicateBehaviors.Skip:
                        Log(LogSeverity.SubStep, "Solution already exists - config instructed a skip");
                        return LookupSolution(farm, solutionName);
                }
            }
            SPSolution solution = farm.Solutions.Add(config.FileName);
            if (config.DeployGlobally)
            {
                solution.Deploy(DateTime.Now, true, true);
                RunAdministrativeTimerJobs();
                AdminServiceTools.RecycleOwssvr();
            }
            return solution;
        }

        public override SPSolution Uninstall(ProcessorContext ctx, Solution config)
        {
            Log(LogSeverity.Step, "Removing solution '{0}' from local farm", config.FileName);

            SPFarm farm = GetFarm();
            if (config.FileName.IsEmpty())
            {
                throw new DeploymentTargetConfigException("Filename cannot be empty for Solution config nodes");
            }

            string solutionName = GetSolutionShortName(config);
            if (!DoesFarmContainSolutionWithName(farm, solutionName))
            {
                switch (AlreadyDeleted)
                {
                    case IgnorableBehaviors.Error:
                        throw new DeploymentTargetConfigException("A solution with the name '" + solutionName + "' could not be found in the solution store - config instructed an error");

                    case IgnorableBehaviors.Ignore:
                        Log(LogSeverity.SubStep, "Could not be found in the solution store - config instructed ignoring it");
                        ctx.ChildrenDeleted = true;
                        return null;
                }
            }

            RemoveSolution(farm, solutionName);
            ctx.ChildrenDeleted = true;
            return null;
        }

        public override SPSolution Update(ProcessorContext ctx, Solution oldConfig, Solution newConfig)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region Methods: private

        private static bool DoesFarmContainSolutionWithName(SPFarm farm, string filename)
        {
            return LookupSolution(farm, filename) != null;
        }

        private static SPFarm GetFarm()
        {
            SPFarm farm = SPFarm.Local;
            if (farm == null)
            {
                throw new DeploymentTargetProcessorException("No local farm found");
            }
            return farm;
        }

        private static string GetSolutionShortName(Solution config)
        {
            return Path.GetFileName(config.FileName);
        }

        private static SPSolution LookupSolution(SPFarm farm, string filename)
        {
            return farm.Solutions[filename];
        }

        private static void RemoveSolution(SPFarm farm, string solutionName)
        {
            farm.Solutions.Remove(solutionName);
        }

        private static void RunAdministrativeTimerJobs()
        {
            AdminServiceTools.AdmJobsStatus status = AdminServiceTools.ExecAdmSvcJobs();
            int retries = 5;
            while (retries-- > 0 && status == AdminServiceTools.AdmJobsStatus.TryAgain)
            {
                status = AdminServiceTools.ExecAdmSvcJobs();
            }
            if (status != AdminServiceTools.AdmJobsStatus.Success)
            {
                throw new DeploymentTargetProcessorException("SolutionDeployment processor failed to execute solution deployment timer jobs");
            }
        }

        #endregion
    }
}